/*
 * apidoc
 * https://apidocjs.com
 *
 * Authors:
 * Peter Rottmann <rottmann@inveris.de>
 * Nicolas CARPi @ Deltablot
 * Copyright (c) 2013 inveris OHG
 * Licensed under the MIT license.
 */
const _ = require("lodash");

const defaultConfig = {
  name: "Acme project",
  version: "0.0.0",
  description: "REST Api",
};

/**
 * Read information about the source code we are parsing from the config file
 */
class Reader {
  constructor(app) {
    this.app = app;
    this.log = app.log;
    this.opt = app.options;
    this.fs = require("fs-extra");
    this.path = require("path");
  }

  // read the apidoc.json file, or apidoc.config.js or from the package.json
  read() {
    let config = {};
    // if the config file is provided, we use this and do no try to read other files
    if (this.opt.config) {
      this.log.debug("Config file provided, reading this.");
      config = require(this.path.resolve(this.opt.config));
    } else {
      config = this.search();
    }
    // replace header footer with file contents
    return Object.assign(config, this.getReadmeMd(config));
  }

  /**
   * Look for config files in input folder
   */
  search() {
    this.log.debug("Now looking for apidoc config files");
    // possible sources of information
    const sources = ["package.json", "apidoc.json", "apidoc.config.js"];

    // create a new object because javascript will not assign value
    const config = Object.assign({}, defaultConfig);

    // loop the three possible source of information to try and find packageInfo
    sources.forEach((configFile) => {
      this.log.debug(`Now looking for ${configFile}`);
      // first look in cwd dir
      Object.assign(
        config,
        this.findConfigFileInDir(configFile, process.cwd())
      );
      // scan each source dir to find a valid config file
      this.opt.src.forEach((dir) => {
        Object.assign(config, this.findConfigFileInDir(configFile, dir));
      });
    });
    if (_.isEqual(config, defaultConfig)) {
      this.log.warn("No config files found.");
    }
    return config;
  }

  /**
   * Get markdown content (from file)
   *
   * @param {Object} config
   * @returns {Object}
   */
  getReadmeMd(config) {
    const result = {};

    config.docs.forEach((doc) => {
      const key = doc.filename.split(".")[0];

      this.log.debug("Now looking for " + key);
      // note that markdown files path is taken from first input value
      let filePath = this.path.join(
        config.input ? config.input[0] : "./",
        doc.filename
      );

      // try again to find it in current dir
      if (!this.fs.existsSync(filePath)) {
        filePath = this.path.join(process.cwd(), doc.filename);
      }

      // try again to find it in input folders
      if (!this.fs.existsSync(filePath)) {
        filePath = this.findFileInSrc(doc.filename);
      }

      // try again to find it in dir with the config file
      if (
        !this.fs.existsSync(filePath) &&
        typeof this.opt.config === "string"
      ) {
        filePath = this.path.join(
          this.path.dirname(this.opt.config),
          doc.filename
        );
      }

      try {
        this.log.debug(`Reading ${key} file: ${filePath}`);
        const content = this.fs.readFileSync(filePath, "utf8");
        const _content = this.app.markdownParser
          ? this.app.markdownParser.render(content)
          : content;

        let newContent = "";

        // 对 _content 拆分，将 h3 匹配出来对其及内容包裹一个 div 标签
        if (_content.includes("<h3>")) {
          const list = _content.split("<h3>");
          const len = list.length;

          for (let i = 0; i < len; i++) {
            const _ctx = list[i];
            if (i === 0) {
              newContent = _ctx;
            } else {
              if (i === 1) {
                newContent += `<div class="doc-card"><h3>${_ctx}`;
              } else {
                if (_ctx.includes("<h2>")) {
                  const list = _ctx.split("<h2>");
                  newContent += `</div>
                                  <div class="doc-card"><h3>${list[0]}</div>
                                  <h2>${list[1]}`;
                } else {
                  newContent += `</div><div class="doc-card"><h3>${_ctx}`;
                }
              }
            }
          }
          newContent += `</div>`;
        } else {
          newContent = _content;
        }

        result[key] = {
          title: doc.title,
          name: key,
          type: "doc",
          content: newContent,
        };
      } catch (e) {
        throw new Error("Can not read: " + filePath);
      }
    });

    return result;
  }

  /**
   * Scan a directory for config files
   */
  findConfigFileInDir(filename, dir) {
    let foundConfig;
    const target = this.path.resolve(this.path.join(dir, filename));
    if (this.fs.existsSync(target)) {
      this.log.debug(`Found file: ${target}`);
      foundConfig = require(target);
      // if it has an apidoc key, read that
      if (foundConfig.apidoc) {
        this.log.verbose(`Using apidoc key of ${filename}`);
        // pull any missing config from root
        ["version", "name", "description"].forEach((key) => {
          if (!foundConfig.apidoc[key] && founddoc) {
            this.log.verbose(`Using ${key} from root of ${filename}`);
            foundConfig.apidoc[key] = founddoc;
          }
        });
        return foundConfig.apidoc;
      }
      // for package.json we don't want to read it if it has no apidoc key
      if (filename !== "package.json") {
        return foundConfig;
      }
    }
    return {};
  }

  /**
   * Look for a file in each of the input folders
   */
  findFileInSrc(filename) {
    // scan each source dir to find a valid config file
    // note that any file found here will supersede a previously found file
    for (const dir of this.opt.src) {
      const target = this.path.join(dir, filename);
      if (this.fs.existsSync(target)) {
        this.log.debug("Found file: " + target);
        return this.path.resolve(target);
      }
    }
    return "";
  }
}

module.exports = {
  Reader: Reader,
  defaultConfig: defaultConfig,
};
